;;  Copyright (c) Jeffrey Straszheim. All rights reserved.  The use and
;;  distribution terms for this software are covered by the Eclipse Public
;;  License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
;;  be found in the file epl-v10.html at the root of this distribution.  By
;;  using this software in any fashion, you are agreeing to be bound by the
;;  terms of this license.  You must not remove this notice, or any other,
;;  from this software.
;;
;;  datalog.clj
;;
;;  A Clojure implementation of Datalog
;;
;;  straszheimjeffrey (gmail)
;;  Created 13 Feburary 2009


(ns clojure.datalog.datalog
  (:require [clojure.datalog.rules :as rules])
  (:require [clojure.datalog.negation :as negation]))

(defmacro logic-rule
  "Builds a rule"
  [& body]
  `(rules/logic-rule ~@body))

(defmacro logic-query
  "Builds a rule"
  [& body]
  `(rules/logic-query ~@body))

(defstruct work-plan
  :work-plan
  :query)

(defn build-work-plan
  "Build a work-plan to execute the query against the rules"
  [rules query]
  (struct-map work-plan
      :work-plan (negation/build-work-plan rules)
      :query query))

(defn run-work-plan
  "Run a work plan against a database"
  [plan database]
  (assert (-> plan :query :query))
  (let [results (negation/run-work-plan (:work-plan plan) database)
        relation (results (-> plan :query :predicate))]
    (rules/match-literal (:query plan) [{}] relation)))

    
(comment

(def test-data
     {
      :employees
      #{
        { :id 1 :name "Sally" :position :boss              :supervisor nil :salary 100000 }
        { :id 2 :name "Fred"  :position :accountant        :supervisor 1   :salary 90000  }
        { :id 3 :name "Janet" :position :accountant        :supervisor 2   :salary 75000  }
        { :id 4 :name "Mike"  :position :senior-programmer :supervisor 1   :salary 80000  }
        { :id 5 :name "Sue"   :position :programmer        :supervisor 4   :salary 65000  }
        { :id 6 :name "Jorge" :position :technition        :supervisor 4   :salary 60000  }
        { :id 7 :name "Jack"  :position :sales             :supervisor 1   :salary 77000  }
        { :id 8 :name "Jan"   :position :sales             :supervisor 7   :salary 65000  }
        { :id 9 :name "Becky" :position :project-manager   :supervisor 1   :salary 60000  }}
      :jobs
      #{
        { :id :payroll            :name "Payroll"              }
        { :id :accounting         :name "Accounting"           }
        { :id :software-dev       :name "Software Development" }
        { :id :computer-support   :name "Computer Support"     }
        { :id :server-support     :name "Server Support"       }
        { :id :sales              :name "Sales"                }
        { :id :project-management :name "Project Management"   }}
      :can-do
      #{
        { :does :software-dev   :can-do :computer-support }
        { :does :server-support :can-do :computer-support }
        { :does :accounting     :can-do :payroll}}
      :main-job
      #{
        { :empl-id 2 :job-id :accounting         }
        { :empl-id 3 :job-id :accounting         }
        { :empl-id 4 :job-id :software-dev       }
        { :empl-id 5 :job-id :software-dev       }
        { :empl-id 6 :job-id :server-support     }
        { :empl-id 7 :job-id :sales              }
        { :empl-id 8 :job-id :sales              }
        { :empl-id 9 :job-id :project-management }}})
        
(def test-rules
     [(logic-rule (:outranked-by :id ?x :super ?y) - (:employees :id ?x :supervisor ?y))

      (logic-rule (:outranked-by :id ?x :super ?y) -
                    (:outranked-by :id ?x :super ?z)
                    (:outranked-by :id ?z :super ?y))

      (logic-rule (:outranked-names :name ?x :super-name ?y) -
                    (:outranked-by :id ?u :super ?v)
                    (:employees :name ?x :id ?u)
                    (:employees :name ?y :id ?v))

      (logic-rule (:can-do-job :empl-id ?x :job-id ?y) - (:main-job :empl-id ?x :job-id ?y))

      (logic-rule (:can-do-job :empl-id ?x :job-id ?y) -
                    (:can-do-job :empl-id ?x :job-id ?z)
                    (:can-do :does ?z :can-do ?y))

      (logic-rule (:can-do-job :empl-id ?x :job-id ?y) -
                    (:main-job :job-id ?y)
                    (:employees :id ?x :position ?z)
                    (if = ?z :boss))

      (logic-rule (:by-name :employee ?x :job ?y) -
                     (:employees :name ?x :id ?empl-id)
                     (:jobs      :name ?y :id ?job-id)
                     (:can-do-job :empl-id ?empl-id :job-id ?job-id))

      (logic-rule (:well-paid :name ?x :salary ?y) - (:employees :name ?x :salary ?y) (if > ?y 75000))
])

(def test-plan-1 (build-work-plan test-rules (logic-query :well-paid :name ?x :salary ?y)))
(run-work-plan test-plan-1 test-data)

(def test-plan-2 (build-work-plan test-rules (logic-query :by-name :employee ?x :job ?y)))
(run-work-plan test-plan-2 test-data)

(def test-plan-3 (build-work-plan test-rules (logic-query :by-name :employee "Sally" :job ?y)))
(run-work-plan test-plan-3 test-data)

)

;; End of file